/*************************************************************************
 * @file: face.cpp
 * @date: 01/04/2012
 * TOPOL v1_0 - (c)copyright 2012 TEC/UFF
 *************************************************************************
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 ************************************************************************/

// includes
//----------
#include "hedmodel.h"
#include "entity/solid.h"
#include "entity/face.h"
#include "entity/halfedge.h"
#include "entity/loop.h"
#include "entity/vertex.h"
#include "entity/edge.h"
#include "entity/ipoint.h"
#include "../mesh/meshgenerator.h"
#include <iostream>
#include <math.h>

// definitions
//-------------
#define PI 3.14159265


///////////////////////////////////////////////////
///  Constructor and Destructor Implementation  ///
///////////////////////////////////////////////////

//-------------------------------------------------------------------------
HEDModel::HEDModel()
: m_firsts( NULL )
{

}
//-------------------------------------------------------------------------



///////////////////////////////////////
///  PUBLIC Methods Implementation  ///
///////////////////////////////////////

//-------------------------------------------------------------------------
Solid* 
HEDModel::getFirsts()
{
	return m_firsts;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Solid*
HEDModel::getSolid( int _sn )
{
	Solid* s = NULL;
	for( s = m_firsts; s != NULL; s = s->getNexts() )
	{
		if( s->getSolidno() == _sn )
			return(s);
	}
	return (NULL);
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Face* 
HEDModel::fface( Solid* _s, int _fn )
{
	Face* f = NULL;
	for( f = _s->getFace(); f != NULL; f = f->getNextf() )
	{
		if( f->getFaceno() == _fn )
			return (f);
	}
	return (NULL);
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
HalfEdge* 
HEDModel::fhe( Face* _f, int _vn1, int _vn2 )
{
	Loop* l = NULL;
	HalfEdge* h = NULL;
	for(l = _f->getFloops(); l != NULL; l = l->getNextl())
	{
		h = l->getLedg();
		do
		{
			if((h->getVtx()->getVertexno() == _vn1) && (h->getNxt()->getVtx()->getVertexno() == _vn2))
			return (h);
		}
		while((h = h->getNxt()) != l->getLedg());
	}
	return (NULL);
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Vertex* 
HEDModel::getVertex( int _vn )
{
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Vertex* v = s->getVertex(); v != NULL; v = v->getNextVertex())
    {
      if(v->getVertexno() == _vn)
      {
        return v;
      }
    }
  }
  return( NULL );
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Face* 
HEDModel::getFace( double _x, double _y, double _z, double _tol )
{
  // TODO: first checks if is in the external face (id=1)
  // use bounding boxes first, then afterwards the ray-shooting procedure
  Face* outFace = NULL;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    double xinters;
    for(Face* f = s->getFace(); f != NULL; f = f->getNextf())
    {
      int nHE = 0;
      int counter = 0;
      Loop* l = f->getFloops();
      Loop* lout = f->getLoopOut();
      if( lout == NULL ) // if external face
      {
        outFace = f;
        continue;
      }
      HalfEdge* h = lout->getLedg();
      do
      {
        nHE++;
        IPoint* pt1 = h->getVtx()->getPoint();
        IPoint* pt2 = h->getNxt()->getVtx()->getPoint();
        int numbV1 = h->getVtx()->getVertexno();
        int numbV2 = h->getNxt()->getVtx()->getVertexno();
        if (_y > min(pt1->getY(),pt2->getY()))
        {
          if (_y <= max(pt1->getY(),pt2->getY())) 
          {
            if (_x <= max(pt1->getX(),pt2->getX())) 
            {
              if (pt1->getY() != pt2->getY()) 
              {
                xinters = (_y-pt1->getY())*(pt2->getX()-pt1->getX())/(pt2->getY()-pt1->getY())+pt1->getX();
                if (pt1->getX() == pt2->getX() || _x <= xinters)
                  counter++;
              }
            }
          }
        }
      }
      while( (h = h->getNxt()) != lout->getLedg() );
      if( counter % 2 != 0 )
      {
        return ( f );
      }
    }
  }
  return outFace;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Edge* 
HEDModel::getEdge( double _x, double _y, double _z, double _tol )
{
  // TODO procurar quais das arestas esta mais proximo do ponto informado
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Edge* e = s->getEdge(); e != NULL; e = e->getNextEdge())
    {
      IPoint* pt1 = e->getFirstVertex()->getPoint();
      IPoint* pt2 = e->getSeconVertex()->getPoint();
      double d0 = sqrt( pt1->distance2(pt2->getX(),pt2->getY(),pt2->getZ()) );
      double d1 = sqrt( pt1->distance2(_x,_y,_z) );
      double d2 = sqrt( pt2->distance2(_x,_y,_z) );
      if( fabs(d1+d2-d0) < 0.06*_tol )
      {
        return e;
      }
    }
  }
  return NULL;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
Vertex* 
HEDModel::getVertex( double _x, double _y, double _z, double _tol )
{
  // TODO procurar quais dos vertices esta mais proximo do ponto informado
  double tol2 = _tol*_tol;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Vertex* v = s->getVertex(); v != NULL; v = v->getNextVertex())
    {
      IPoint* pt = v->getPoint();
      if( pt->distance2(_x,_y,_z) < tol2 )
      {
        return v;
      }
    }
  }
  return NULL;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
size_t 
HEDModel::getNumberOfFaces()
{
  // counts the number of faces in the model
  size_t numFaces = 0;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Face* f = s->getFace(); f != NULL; f = f->getNextf())
    {
      numFaces++;
    }
  }
  return numFaces;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
size_t 
HEDModel::getNumberOfVertexes()
{
  // counts the number of vertexes in the model
  size_t numVtxs = 0;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Vertex* v = s->getVertex(); v != NULL; v = v->getNextVertex())
    {
      numVtxs++;
    }
  }
  return numVtxs;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
vector<Face*> 
HEDModel::getFaces()
{
  vector<Face*> faces;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Face* f = s->getFace(); f != NULL; f = f->getNextf())
    {
      faces.push_back( f );
    }
  }
  return faces;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
vector<Edge*> 
HEDModel::getEdges()
{
  vector<Edge*> edges;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Edge* e = s->getEdge(); e != NULL; e = e->getNextEdge())
    {
      edges.push_back( e );
    }
  }
  return edges;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
vector<Vertex*> 
HEDModel::getVertexes()
{
  vector<Vertex*> vtxs;
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Vertex* v = s->getVertex(); v != NULL; v = v->getNextVertex())
    {
      vtxs.push_back( v );
    }
  }
  return vtxs;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
HalfEdge* 
HEDModel::getHalfEdge( Vertex* _v, double _x, double _y, double _z )
{
  HalfEdge* he_i = _v->getVEdge();
  if( he_i->getEdg() != NULL )
  {
    // counts how many edges this vertex contains:
    int nEdges = 1;
    while( (he_i= he_i->mate()->getNxt()) != _v->getVEdge() )
      nEdges++;
    // case the vertex contains only one edge then returns its half-edge,
    // otherwise returns the half-edge that is most right of the "new edge"
    if( nEdges > 1 )
    {
      // computes the angle with the horizontal for the "new edge"
      double deltaX = _x - _v->getPoint()->getX();
      double deltaY = _y - _v->getPoint()->getY();
      double angleRef = 0.0;
      double angle_min = 2.0*PI;
      HalfEdge* he_min = NULL;
      if( deltaX != 0 )  // different of 90 and 270 degrees
      {
        angleRef = atan(deltaY/deltaX); 
        // correct angle (to positive value) in case a negative dimension
        if( deltaX < 0 )
          angleRef = angleRef + PI;
        else if( deltaY < 0 )
          angleRef = angleRef + PI*2;
      }
      else if( deltaY > 0 )  // 90 degrees
        angleRef = PI/2.0;
      else  // 270 degrees
        angleRef = PI*3.0/2.0;
      //std::cout << "ANG REF = " << angleRef << std::endl;
      // loops over the vertex edges to identify the desired half-edge
      he_i = _v->getVEdge();
      do
      {
        // computes the angle with the horizontal for the "current edge"
        double deltaX_i = he_i->mate()->getVtx()->getPoint()->getX() - _v->getPoint()->getX();
        double deltaY_i = he_i->mate()->getVtx()->getPoint()->getY() - _v->getPoint()->getY();
        double angle_i = 0.0;
        if( deltaX_i != 0 )  // different of 90 and 270 degrees
        {
          angle_i = atan(deltaY_i/deltaX_i); 
          // correct angle (to positive value) in case a negative dimension
          if( deltaX_i < 0 )
            angle_i = angle_i + PI;
          else if( deltaY_i < 0 )
            angle_i = angle_i + PI*2;
        }
        else if( deltaY_i > 0 )  // 90 degrees
          angle_i = PI/2.0;
        else  // 270 degrees
          angle_i = PI*3.0/2.0;
        //std::cout << "ANG i = " << angle_i << std::endl;
        // obtains only positive values from reference edge in ccw 
        angle_i = angleRef - angle_i;
        if( angle_i < 0 )
          angle_i = angle_i + 2.0*PI;
        if( angle_i < angle_min )
        {
          angle_min = angle_i;
          he_min = he_i;
        }
      }
      while ( (he_i= he_i->mate()->getNxt()) != _v->getVEdge() );
      return he_min;
    } 
    else
    {
      return he_i;
    }
  }
  else
  {
    return he_i;
  }
  return he_i;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDModel::setFirsts( Solid* _firsts )
{
  m_firsts = _firsts;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDModel::triagulate( vector<IPoint>& _p, vector<vector<int> >& _t )
{
  vector<IPoint> pts;
  MeshGenerator mesher;
  mesher.generateMesh( _p, pts, _t );
  _p = pts;

/*
  // triangulate(polygon *p, triangulation *t)
  int counter = 0;
  int pn = _p.size();
  int l[50], r[50]; // left/right neighbor indices
  int i; // counter
  for( i=0; i<pn; i++ ) 
  { // initialization 
    l[i] = ((i-1) + pn) % pn;
    r[i] = ((i+1) + pn) % pn;
  }
  i = pn-1;
  while(_t.size() < (pn-2)) 
  {
    i = r[i];
    if (ear_Q(l[i],i,r[i],_p)) 
    {
      vector<int> tri(3);
      tri[0] = l[i];
      tri[1] = i;
      tri[2] = r[i];
      _t.push_back( tri );
      //add_triangle(t,l[i],i,r[i],p);
      l[ r[i] ] = l[i];
      r[ l[i] ] = r[i];
    }
    // stops in case it has problems with tolerance: (TODO)
    if(++counter>100*pn)
      break;
  }
*/
}
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
Edge* 
HEDModel::getEdge( int _v1, int _v2 )
{
  for(Solid* s = m_firsts; s != NULL; s = s->getNexts())
  {
    for(Edge* e = s->getEdge(); e != NULL; e = e->getNextEdge())
    {
      int v1 = e->getFirstVertex()->getVertexno();
      int v2 = e->getSeconVertex()->getVertexno();
      if( (v1 == _v1) && (v2 == _v2) )
      {
        return e;
      }
      if( (v2 == _v1) && (v1 == _v2) )
      {
        return e;
      }
    }
  }
  return NULL;

}
//-------------------------------------------------------------------------


////////////////////////////////////////
///  PRIVATE Methods Implementation  ///
////////////////////////////////////////

//-------------------------------------------------------------------------
bool 
HEDModel::ear_Q( int _i, int _j, int _k, vector<IPoint*>& _p )
{
  vector<IPoint*> t(3); // coordinates for points i,j,k 
  int m; // counter
  t[0] = _p[_i];
  t[1] = _p[_j];
  t[2] = _p[_k];
  //copy_point(p->p[i],t[0]);
  //copy_point(p->p[j],t[1]);
  //copy_point(p->p[k],t[2]);
  if( cw(t[0],t[1],t[2]) ) 
    return (false);
  for( m=0; m<_p.size(); m++ ) 
  {
    if( (m!=_i) && (m!=_j) && (m!=_k) )
      if( point_in_triangle(_p[m],t) ) 
        return (false);
  }
  return (true);
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
bool 
HEDModel::point_in_triangle( IPoint* _p, vector<IPoint*>& _t )
{
  for( int i=0; i<3; i++ )
    if( cw(_t[i],_t[(i+1)%3],_p) ) 
      return( false );
  return( true );
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
bool 
HEDModel::cw( IPoint* _a, IPoint* _b, IPoint* _c )
{
  return( signed_triangle_area(_a,_b,_c) < 1.0e-5 );
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
double
HEDModel::signed_triangle_area( IPoint* _a, IPoint* _b, IPoint* _c )
{
  return( ( _a->getX()*_b->getY() - _a->getY()*_b->getX()
          + _a->getY()*_c->getX() - _a->getX()*_c->getY()
          + _b->getX()*_c->getY() - _c->getX()*_b->getY()) / 2.0 );
}
//-------------------------------------------------------------------------

